/*
 * wrapper.cxx
 *
 * OpenH323 Wrapper Library
 *
 * Copyright (c) 2002-2005 InAccess Networks
 * Michalis Manousos <manousos@inaccessnetworks.com>
 * Dimitris Economou <decon@inaccessnetworks.com>
 *
 * This file is part of "H.323 support for ASTERISK"
 *
 * "H.323 support for ASTERISK" is free software;
 * you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or (at your option) any later version. 
 *
 * "H.323 support for ASTERISK" is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. 
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 * $Id: wrapper.cxx,v 1.2 2010/05/04 09:02:57 mmanousos Exp $
 *
 */

/************************************************************************/
/* INCLUDE FILES ********************************************************/

#include <ptlib.h>
#include <h323.h>
#include <h323pdu.h>
#include <mediafmt.h>
#include <lid.h>
#include <gkserver.h>

#include <list>
#include <string>
#include <algorithm>

#define cplusplus
#include "version.h"
#include "wrapcaps.hxx"
#include "wrapper_misc.hxx"
#include "wrapper.hxx"
#include "wrapendpoint.hxx"
#include "wrapconnection.hxx"
#include "wrapgkserver.hxx"
#include "asteriskaudio.hxx"

using namespace std;

/************************************************************************/
/* Wrapper's internal variables and classes *****************************/

#ifdef	WRAPTRACING
int wrapTraceLevel;
#endif

/* These are set by the application */
static int version_maj = 0;
static int version_min = 0;
static int version_bld = 0;
static char app_name[128] = { 0 };

/* Callback functions */
start_logchan_cb	on_start_logical_channel; 
clear_con_cb		on_connection_cleared;
alert_con_cb		on_connection_alert;
h323_exception_cb	on_h323_exception;
init_con_cb			on_connection_init;
stats_con_cb		on_connection_stats;
user_data_cb		on_user_data;

/**
 * The wrapper library assumes that only one endpoint should exist.
 * The application cannot run twice the h323_end_point_create(). 
 */
WrapH323EndPoint *endPoint = NULL;

/**
 * The gatekeeper server object.
 */
WrapGatekeeperServer *gkServer = NULL;



/**************************************************************************
 * The WrapThread is a necessary descendant of PProcess class so that the H323EndPoint 
 * object to be created from within that class. The reason is that the constructor
 * of the H323EndPoint class needs to find some information related to a hypothetically
 * PProcess class which is supposed to instantiates it. 
 */
class WrapProcess : public PProcess {

	PCLASSINFO(WrapProcess, PProcess);

	public:
	WrapProcess(char **gwprefix_tab, int gwprefix_num, int logLev, char *logFile)
	: PProcess("inAccess Networks (www.inaccessnetworks.com)", app_name, 
		version_maj, version_min, ReleaseCode, version_bld)
	{ 
		WRAPTRACE(4, "Going up.");
		endPoint = NULL;
		prefixes = gwprefix_tab;
		prefix_num = gwprefix_num;
		log_lev = logLev;
		if ((logFile == NULL) || (logFile[0] == '\0'))
			log_file = NULL;
		else
			log_file = logFile;
	};

	~WrapProcess()
	{
		WRAPTRACE(4, "Going down.");
		if (endPoint != NULL) {
			delete endPoint;
			endPoint = NULL;
		}
		if (gkServer != NULL) {
			delete gkServer;
			gkServer = NULL;
		}
		PTrace::SetLevel(0);
	};

	void 
	Main()
	{
		WRAPTRACE(4, "Starting...");
		PTrace::Initialise(log_lev, log_file);
#ifdef P_HAS_IPV6
		if (PIPSocket::IsIpAddressFamilyV6Supported())
			PIPSocket::SetDefaultIpAddressFamilyV6();
#endif
		endPoint = new WrapH323EndPoint(prefixes, prefix_num);
		//gkServer = new WrapGatekeeperServer((H323EndPoint &)*endPoint);
		gkServer = NULL;
		return;
	};

	protected:
	char **prefixes;
	int prefix_num;
	int log_lev;
	char *log_file;
};
WrapProcess *localProcess = NULL;


/**************************************************************************
 * This class handles the termination of a call.
 * Note that OpenH323 Library requires that the termination
 * of a call should be done inside a separate thread of execution.
 */
class ClearCallThread : public PThread {

	PCLASSINFO(ClearCallThread, PThread);

	public:
	ClearCallThread(const char *tc)
		: PThread(10000, PThread::AutoDeleteThread)
	{ 
		WRAPTRACE(4, "Object initialized.");
		WRAPTRACE(4, "Unblock pipe - " << unblockPipe[0] << ", " << unblockPipe[1]);
		token = tc;
	};

	~ClearCallThread()
	{
		WRAPTRACE(4, "Object deleted.");
		return;
	};
    
	void 
	Main()
	{
		//endPoint->ClearCall(token);
		//if (endPoint->ClearCallSynchronous(token) == FALSE) {
		if (endPoint->ClearCall(token) == FALSE) {
			WRAPTRACE(2, "Failed to clear call with token " << token);
		} else {
			WRAPTRACE(2, "Call with token " << token << " cleared.");
		}
		return;
	};

	protected:
	PString	token;
};

/**************************************************************************
 * This class handles the registration with a gatekeeper.
 */
class GKRegThread : public PThread {

	PCLASSINFO(GKRegThread, PThread);

	public:
	GKRegThread(const char *gkname, const char *gkzone)
		: PThread(10000)
	{
		WRAPTRACE(4, "Object initialized.");
		WRAPTRACE(4, "Unblock pipe - " << unblockPipe[0] << ", " << unblockPipe[1]);
		if ((gkname == NULL) || (strlen(gkname) == 0))
			gkName = PString::Empty();
		else
			gkName = PString(gkname);
		if ((gkzone == NULL) || (strlen(gkzone) == 0))
			gkZone = PString::Empty();
		else
			gkZone = PString(gkzone);
	};

	~GKRegThread()
	{
		WRAPTRACE(4, "Object deleted.");
		return;
	};

	void 
	Main()
	{
		WRAPTRACE(2, "GK: name [" << gkName << "], zone [" << gkZone << "]");
		if (endPoint->UseGatekeeper(gkName, gkZone)) {
			WRAPTRACE(2, "Using GK name [" << gkName << "], zone [" << gkZone << "]");
		} else {
			WRAPTRACE(2, "Failed to register with GK name [" << gkName << "], zone [" << gkZone << "]");
		}
		return;
	};

	protected:
	PString	gkName;
	PString	gkZone;
};
GKRegThread *gkRegThread = NULL;


/************************************************************************/
/* INTERNAL C-FUNCTIONS OF WRAPPER **************************************/

/**************************************************************************
 * Creates a H323Capability based on the given capability/codec type.
 * Returns a pointer to the capability on success, NULL on failure
 * or unsupported capability.
 */
H323Capability *
h323_capability_create(WrapH323EndPoint *ep, cap_type_t capability, int frame_num)
{
	H323Capability	*newCap;
	int frames;

	newCap = NULL;
	switch (capability) {
		case G711U:
		{
			// G.711 ulaw pass-through codec
			H323_LIDCapability *g711muCap = new H323_LIDCapability(OPAL_G711_ULAW_64K);
			if (frame_num > 0)
				ep->SetFrames(OpalG711uLaw, frame_num);
			frames = ep->GetFrames(OpalG711uLaw);
			if (frames <= 0) {
				delete g711muCap;
				break;
			}
			g711muCap->SetTxFramesInPacket(frames);
			newCap = g711muCap;
			break;
		}
		case G711A:
		{
			// G.711 alaw pass-through codec
			H323_LIDCapability *g711aCap = new H323_LIDCapability(OPAL_G711_ALAW_64K);
			if (frame_num > 0)
				ep->SetFrames(OpalG711ALaw, frame_num);
			frames = ep->GetFrames(OpalG711ALaw);
			if (frames <= 0) {
				delete g711aCap;
				break;
			}
			g711aCap->SetTxFramesInPacket(frames);
			newCap = g711aCap;
			break;
		}
		case G7231:
		case G72316K3:
		{
			// G.723.1(6.3k) pass-through codec
			H323_LIDCapability *g72316k3Cap = new H323_LIDCapability(OPAL_G7231_6k3);
			if (frame_num > 0)
				ep->SetFrames(OpalG7231_6k3, frame_num);
			frames = ep->GetFrames(OpalG7231_6k3);
			if (frames <= 0) {
				delete g72316k3Cap;
				break;
			}
			g72316k3Cap->SetTxFramesInPacket(frames);
			newCap = g72316k3Cap;
			break;
		}
		case G72315K3:
		{
			// G.723.1(5.3k) pass-through codec
			H323_LIDCapability *g72315k3Cap = new H323_LIDCapability(OPAL_G7231_5k3);
			if (frame_num > 0)
				ep->SetFrames(OpalG7231_5k3, frame_num);
			frames = ep->GetFrames(OpalG7231_5k3);
			if (frames <= 0) {
				delete g72315k3Cap;
				break;
			}
			g72315k3Cap->SetTxFramesInPacket(frames);
			newCap = g72315k3Cap;
			break;
		}
		case G7231A6K3:
		{
			// G.723.1A(6.3k) pass-through codec
			H323_LIDCapability *g7231A6k3Cap = new H323_LIDCapability(OPAL_G7231A_6k3);
			if (frame_num > 0)
				ep->SetFrames(OpalG7231A_6k3, frame_num);
			frames = ep->GetFrames(OpalG7231A_6k3);
			if (frames <= 0) {
				delete g7231A6k3Cap;
				break;
			}
			g7231A6k3Cap->SetTxFramesInPacket(frames);
			newCap = g7231A6k3Cap;
			break;
		}
		case G7231A5K3:
		{
			// G.723.1A(5.3k) pass-through codec
			H323_LIDCapability *g7231A5k3Cap = new H323_LIDCapability(OPAL_G7231A_5k3);
			if (frame_num > 0)
				ep->SetFrames(OpalG7231A_5k3, frame_num);
			frames = ep->GetFrames(OpalG7231A_5k3);
			if (frames <= 0) {
				delete g7231A5k3Cap;
				break;
			}
			g7231A5k3Cap->SetTxFramesInPacket(frames);
			newCap = g7231A5k3Cap;
			break;
		}
		case G72616K:
		{
			// G.726(16k) pass-through codec
			Wrap_G726_Capability *g72616kCap = 
							new Wrap_G726_Capability((H323EndPoint&)*ep, WRAP_G726_SPEED16);
			if (frame_num > 0)
				ep->SetFrames(WrapG726_16, frame_num);
			frames = ep->GetFrames(WrapG726_16);
			if (frames <= 0) {
				delete g72616kCap;
				break;
			}
			g72616kCap->SetTxFramesInPacket(frames);
			newCap = g72616kCap;
			break;
		}
		case G72624K:
		{
			// G.726(24k) pass-through codec
			Wrap_G726_Capability *g72624kCap = 
							new Wrap_G726_Capability((H323EndPoint&)*ep, WRAP_G726_SPEED24);
			if (frame_num > 0)
				ep->SetFrames(WrapG726_24, frame_num);
			frames = ep->GetFrames(WrapG726_24);
			if (frames <= 0) {
				delete g72624kCap;
				break;
			}
			g72624kCap->SetTxFramesInPacket(frames);
			newCap = g72624kCap;
			break;
		}
		case G726:
		case G72632K:
		{
			// G.726(32k) pass-through codec
			Wrap_G726_Capability *g72632kCap = 
							new Wrap_G726_Capability((H323EndPoint&)*ep, WRAP_G726_SPEED32);
			if (frame_num > 0)
				ep->SetFrames(WrapG726_32, frame_num);
			frames = ep->GetFrames(WrapG726_32);
			if (frames <= 0) {
				delete g72632kCap;
				break;
			}
			g72632kCap->SetTxFramesInPacket(frames);
			newCap = g72632kCap;
			break;
		}
		case G72640K:
		{
			// G.726(40k) pass-through codec
			Wrap_G726_Capability *g72640kCap = 
							new Wrap_G726_Capability((H323EndPoint&)*ep, WRAP_G726_SPEED40);
			if (frame_num > 0)
				ep->SetFrames(WrapG726_40, frame_num);
			frames = ep->GetFrames(WrapG726_40);
			if (frames <= 0) {
				delete g72640kCap;
				break;
			}
			g72640kCap->SetTxFramesInPacket(frames);
			newCap = g72640kCap;
			break;
		}
		case G728:
		{
			// G.728 pass-through codec
			H323_LIDCapability *g728Cap = new H323_LIDCapability(OPAL_G728);
			if (frame_num > 0)
				ep->SetFrames(OpalG728, frame_num);
			frames = ep->GetFrames(OpalG728);
			if (frames <= 0) {
				delete g728Cap;
				break;
			}
			g728Cap->SetTxFramesInPacket(frames);
			newCap = g728Cap;
			break;
		}
		case G729:
		{
			// G.729 pass-through codec
			H323_LIDCapability *g729Cap = new H323_LIDCapability(OPAL_G729);
			if (frame_num > 0)
				ep->SetFrames(OpalG729, frame_num);
			frames = ep->GetFrames(OpalG729);
			if (frames <= 0) {
				delete g729Cap;
				break;
			}
			g729Cap->SetTxFramesInPacket(frames);
			newCap = g729Cap;
			break;
		}
		case G729A:
		{
			// G.729A pass-through codec
			H323_LIDCapability *g729ACap = new H323_LIDCapability(OPAL_G729A);
			if (frame_num > 0)
				ep->SetFrames(OpalG729A, frame_num);
			frames = ep->GetFrames(OpalG729A);
			if (frames <= 0) {
				delete g729ACap;
				break;
			}
			g729ACap->SetTxFramesInPacket(frames);
			newCap = g729ACap;
			break;
		}
		case G729B:
		{
			// G.729B pass-through codec
			H323_LIDCapability *g729BCap = new H323_LIDCapability(OPAL_G729B);
			if (frame_num > 0)
				ep->SetFrames(OpalG729B, frame_num);
			frames = ep->GetFrames(OpalG729B);
			if (frames <= 0) {
				delete g729BCap;
				break;
			}
			g729BCap->SetTxFramesInPacket(frames);
			newCap = g729BCap;
			break;
		}
		case G729AB:
		{
			// G.729AB pass-through codec
			H323_LIDCapability *g729ABCap = new H323_LIDCapability(OPAL_G729AB);
			if (frame_num > 0)
				ep->SetFrames(OpalG729AB, frame_num);
			frames = ep->GetFrames(OpalG729AB);
			if (frames <= 0) {
				delete g729ABCap;
				break;
			}
			g729ABCap->SetTxFramesInPacket(frames);
			newCap = g729ABCap;
			break;
		}
		case GSM0610:
		{
			// GSM 06.10 pass-through codec
			H323_LIDCapability *gsmCap = new H323_LIDCapability(OPAL_GSM0610);
			if (frame_num > 0)
				ep->SetFrames(OpalGSM0610, frame_num);
			frames = ep->GetFrames(OpalGSM0610);
			if (frames <= 0) {
				delete gsmCap;
				break;
			}
			gsmCap->SetTxFramesInPacket(frames);
			newCap = gsmCap;
			break;
		}
		case MSGSM:
		{
#if 0
			// Use the built-in codec for Microsoft GSM
			MicrosoftGSMAudioCapability * msGsmCap = new MicrosoftGSMAudioCapability;
			msGsmCap->SetTxFramesInPacket(4);
			newCap = msGsmCap;
#endif
			break;
		}
		case LPC10:
		{
#if 0
			// Use the built-in codec for LPC10
			if (ep == NULL)
				break;
			H323_LPC10Capability * lpcCap = new H323_LPC10Capability(*ep);
			newCap = lpcCap;
#endif
			break;
		}
		default:
			// Unknown codec!
			break;
	}
	return(newCap);
}

/************************************************************************/
/* IMPLEMENTATION OF WRAPPER API ****************************************/

/**
 * The extern "C" directive takes care for 
 * the ANSI-C representation of linkable symbols 
 */
extern "C" {

	static struct {
		int h323_reason;
		int wrap_reason;
		const char *desc;
	} call_end_reason[] = {
		{ H323Connection::EndedByLocalUser, OH323END_LOCAL_USER, "Cleared by local user" },
		{ H323Connection::EndedByNoAccept, OH323END_NO_ACCEPT, "Incoming call was not accepted" },
		{ H323Connection::EndedByAnswerDenied, OH323END_ANSWER_DENIED, "Incoming call denied" },
		{ H323Connection::EndedByRemoteUser, OH323END_REMOTE_USER, "Cleared by remote user" },
		{ H323Connection::EndedByRefusal, OH323END_REFUSAL, "Remote user refused call" },
		{ H323Connection::EndedByNoAnswer, OH323END_NO_ANSWER, "Remote user did not answer call" },
		{ H323Connection::EndedByCallerAbort, OH323END_CALLER_ABORT, "Remote user stopped calling" },
		{ H323Connection::EndedByTransportFail, OH323END_TRANSPORT_FAIL, "Transport failure" },
		{ H323Connection::EndedByConnectFail, OH323END_CONNECT_FAIL, "Connection failure" },
		{ H323Connection::EndedByGatekeeper, OH323END_GATEKEEPER, "Gatekeeper cleared call" },
		{ H323Connection::EndedByNoUser, OH323END_NO_USER, "Gatekeeper could not find user" },
		{ H323Connection::EndedByNoBandwidth, OH323END_NO_BANDWIDTH, "Not enough bandwidth" },
		{ H323Connection::EndedByCapabilityExchange, OH323END_CAPABILITY, "No common codecs" },
		{ H323Connection::EndedByCallForwarded, OH323END_CALLFWD, "Call was forwarded" },
		{ H323Connection::EndedBySecurityDenial, OH323END_SECURITY, "Call ended due to security checks" },
		{ H323Connection::EndedByLocalBusy, OH323END_LOCAL_BUSY, "Local endpoint is busy" },
		{ H323Connection::EndedByLocalCongestion, OH323END_LOCAL_CONGESTION, "Local endpoint is congested" },
		{ H323Connection::EndedByRemoteBusy, OH323END_REMOTE_BUSY, "Remote endpoint is busy" },
		{ H323Connection::EndedByRemoteCongestion, OH323END_REMOTE_CONGESTION, "Remote endpoint is congested" },
		{ H323Connection::EndedByUnreachable, OH323END_UNREACHABLE, "Remote endpoint is unreachable" },
		{ H323Connection::EndedByNoEndPoint, OH323END_NO_ENDPOINT, "No endpoint" },
		{ H323Connection::EndedByHostOffline, OH323END_HOST_OFFLINE, "Remote endpoint is offline" },
		{ H323Connection::EndedByTemporaryFailure, OH323END_TEMP_FAILURE, "Temporary failure" },
		{ H323Connection::EndedByQ931Cause, OH323END_Q931CAUSE, "Call ended with Q.931 cause" },
		{ H323Connection::EndedByDurationLimit, OH323END_DURATION_LIMIT, "Call ended due to enforced duration limit" },
		{ H323Connection::EndedByInvalidConferenceID, OH323END_INVALIDCID, "Call cleared due to invalid conference ID" },
		{ -1, -1, "Unknown reason" },
	};

	static struct {
		int h323_cause;
		const char *desc;
	} call_cause[] = {
		{ Q931::UnknownCauseIE, "Unknown cause" },
		{ Q931::UnallocatedNumber, "Unallocated/Unassigned number" },
		{ Q931::NoRouteToNetwork, "No route to transit network" },
		{ Q931::NoRouteToDestination, "No route to destination" },
		{ Q931::SendSpecialTone, "Send special tone" },
		{ Q931::MisdialledTrunkPrefix, "Misdialled trunk prefix" },
		{ Q931::ChannelUnacceptable, "Channel unacceptable" },
		{ Q931::NormalCallClearing, "Normal call clearing" },
		{ Q931::UserBusy, "User busy" },
		{ Q931::NoResponse, "No user responding" },
		{ Q931::NoAnswer, "User alerting, no answer" },
		{ Q931::SubscriberAbsent, "Subscriber absent" },
		{ Q931::CallRejected, "Call rejected" },
		{ Q931::NumberChanged, "Number changed" },
		{ Q931::Redirection, "Redirection" },
		{ Q931::ExchangeRoutingError, "Exchange routing error" },
		{ Q931::NonSelectedUserClearing, "Non-selected user clearing" },
		{ Q931::DestinationOutOfOrder, "Destination out of order" },
		{ Q931::InvalidNumberFormat, "Invalid number format" },
		{ Q931::FacilityRejected, "Facility rejected" },
		{ Q931::StatusEnquiryResponse, "Response to STATUS ENQUIRY" },
		{ Q931::NormalUnspecified, "Normal, unspecified" },
		{ Q931::NoCircuitChannelAvailable, "No circuit/channel available" },
		{ Q931::NetworkOutOfOrder, "Network out of order" },
		{ Q931::TemporaryFailure, "Temporary failure" },
		{ Q931::Congestion, "Switching equipment congestion" },
		{ Q931::RequestedCircuitNotAvailable, "Requested circuit/channel not available" },
		{ Q931::ResourceUnavailable, "Resource unavailable, unspecified" },
		{ Q931::ServiceOptionNotAvailable, "Service or option not available, unspecified" },
		{ Q931::InvalidCallReference, "Invalid call reference value" },
		{ Q931::ClearedRequestedCallIdentity, "Call having the requested call identity has been cleared" },
		{ Q931::IncompatibleDestination, "Incompatible destination" },
		{ Q931::IENonExistantOrNotImplemented, "Information element non-existent or not implemented" },
		{ Q931::TimerExpiry, "Recovery on timer expired" },
		{ Q931::ProtocolErrorUnspecified, "Protocol error, unspecified" },
		{ Q931::InterworkingUnspecified, "Interworking, unspecified" },
		{ Q931::ErrorInCauseIE, "Error in cause IE" },
#ifndef	USE_OLD_CAPABILITIES_API
		{ Q931::BearerCapNotImplemented, "Bearer capability not implemented" },
		{ Q931::ChannelTypeNotImplemented, "Channel type not implemented" },
		{ Q931::RequestedFacilityNotImplemented, "Requested facility not implemented" },
		{ Q931::OnlyRestrictedDigitalBearerCapAvailable, "Only restricted digital information bearer capability is available (national use)" },
		{ Q931::ServiceOrOptionNotImplemented, "Service or option not implemented, unspecified" },
		{ Q931::PrecedenceCallBlocked, "Precedence call blocked" },
		{ Q931::CallAwarded, "Call awarded and being delivered in an established channel" },
		{ Q931::Preemption, "Preemtion" },
		{ Q931::PreemptionCircuitReserved, "Preemtion circuit reserved" },
		{ Q931::CallQueued, "Call queued" },
		{ Q931::FrameModeOOS, "Permanent frame mode connection out of service" },
		{ Q931::FrameModeOperational, "Permanent frame mode connection operational" },
		{ Q931::AccessInformationDiscarded, "Access information discarded" },
		{ Q931::QoSNotAvailable, "Quality of service unavailable" },
		{ Q931::RequestedFacilityNotSubscribed, "Requested facility not subscribed" },
		{ Q931::OutgoingCallsBarred, "Outgoing calls barred" },
		{ Q931::OutgoingCallsBarredInCUG, "Outgoing calls barred withing Closed User Group" },
		{ Q931::IncomingCallsBarred, "Incoming calls barred" },
		{ Q931::IncomingCallsBarredInCUG, "Incoming calls barred withing Closed User Group" },
		{ Q931::BearerCapNotAuthorised, "Bearer capability not authorized" },
		{ Q931::BearerCapNotPresentlyAvailable, "Bearer capability not presently available" },
		{ Q931::InconsistentOutgoingIE, "Inconsistency in designated outgoing access information and subscriber class" },
		{ Q931::IdentifiedChannelNonExistent, "Identified channel does not exist" },
		{ Q931::CallIdentifyNotSuspendedCall, "A suspended call exists, but this call identity does not" },
		{ Q931::CallIdentifyInUse, "Call identity in use" },
		{ Q931::NoCallSuspended, "No call suspended" },
		{ Q931::UserNotInCUG, "Called user not member of Closed User Group" },
		{ Q931::NonexistentCUG, "Non-existent Closed User Group" },
		{ Q931::InvalidTransitNetwork, "Invalid transit network selection (national use)" },
		{ Q931::InvalidMessageUnspecified, "Invalid message, unspecified" },
		{ Q931::MandatoryIEMissing, "Mandatory information element is missing" },
		{ Q931::MessageTypeNonexistent, "Message type non-existent or not implemented" },
		{ Q931::MessageNotCompatible, "Message is not compatible with the call state, or the message type is non-existent or not implemented" },
		{ Q931::InvalidIEContents, "Invalid information element contents" },
		{ Q931::MessageNotCompatibleWithCallState, "The message is not compatible with the call state" },
		{ Q931::ParameterNonexistent, "Parameter non-existent or not implemented passed on (national use)" },
		{ Q931::UnrecognisedParamaterDiscarded, "Message with unrecognized parameter discarded" },
#endif
		{ -1, "Unknown cause value" },
	};

	/**************************************************************************
	 * Check whether the H.323 endpoint has already been created
	 */
	boolean_t 
	end_point_exist(void)
	{
		if (endPoint == NULL) {
			return NO;
		}
		return YES;
	}
 
	/**************************************************************************
     *
     */
	void
	h323_appinfo_set(char *name, int major, int minor, int build)
	{
		memset(app_name, 0, sizeof(app_name));
		strncpy(app_name, name, sizeof(app_name) - 1);
		version_maj = major;
		version_min = minor;
		version_bld = build;
	}

	/**************************************************************************
	 * Create the H.323 endpoint
	 */
	void 
	h323_end_point_create(char **gwprefix_tab, int gwprefix_num, 
			int wrap_log_lev, int log_lev, char *log_file)
	{
		if (end_point_exist() == YES) {
			WRAPTRACEAPI(1, "Endpoint exists! Destroy it first.");
			return;
		}

		on_start_logical_channel = NULL;
		on_connection_cleared = NULL;
		on_connection_alert = NULL;
		on_h323_exception = NULL;
		on_connection_init = NULL;
		on_user_data = NULL;
		on_connection_stats = NULL;
		channelsOpen = 0;
#ifdef	WRAPTRACING
		wrapTraceLevel = wrap_log_lev;
#endif
		localProcess = new WrapProcess(gwprefix_tab, gwprefix_num, log_lev, log_file);
		localProcess->Main();
		WRAPTRACEAPI(2, "Endpoint created.");
	}

	/**************************************************************************
	 * Destroy the H.323 endpoint
	 */
	void 
	h323_end_point_destroy(void)
	{
		WRAPTRACEAPI(2, "Destroying endpoint.");
		if (end_point_exist() == NO) {
			return;
		}

		/* Cleanup stuff */
		//endPoint->ClearAllCalls();

		if (gkRegThread != NULL) {
			gkRegThread->WaitForTermination();
			delete gkRegThread;
			gkRegThread = NULL;
		}

		if (localProcess != NULL) {
			//localProcess->WaitForTermination();
			delete localProcess;
			localProcess = NULL;
		}
	}

	/**************************************************************************
	 * Install the callback functions
	 */
	int 
	h323_callback_register(start_logchan_cb sfunc, clear_con_cb cfunc,
						alert_con_cb alertfunc, h323_exception_cb exfunc,
						init_con_cb initfunc, user_data_cb userfunc,
						stats_con_cb statsfunc)
	{
		on_start_logical_channel = sfunc;
		on_connection_cleared = cfunc;
		on_connection_alert = alertfunc;
		on_h323_exception = exfunc;
		on_connection_init = initfunc;
		on_user_data = userfunc;
		on_connection_stats = statsfunc;
		WRAPTRACEAPI(3, "Callback functions installed.");
		return(0);
	}

	/**************************************************************************
	 * Add the specified capability to the capabilities table of the H.323 endpoint 
	 * Note: AddCapability puts the codec into the list of codecs we can send
	 */
	cap_ret_val_t
	h323_add_capability(cap_type_t cap, int frames)
	{
		H323Capability	*h323Cap;
		cap_ret_val_t	ret_val;

		if (end_point_exist() == NO) {
			return CAP_EP_ER;
		}

		ret_val = CAP_NSUP_ER;

		/* Create and add the capability */
		h323Cap = h323_capability_create(endPoint, cap, frames);
		if (h323Cap != NULL) {
			endPoint->AddCapability(h323Cap);
			WRAPTRACEAPI(2, "Added capability " << h323Cap->GetFormatName());
			ret_val = CAP_INSERT_OK;
		} else {
			WRAPTRACEAPI(2, "Failed to add capability type " << cap);
			ret_val = CAP_NSUP_ER;
		}
		return ret_val;
	}

	/**************************************************************************
	 * Set the specified capability as the first capability of the H.323 endpoint
	 * Note: SetCapability puts the codec into the list of codecs we can send and receive
	 */
	cap_ret_val_t 
	h323_set_capability(cap_type_t cap, int frames)
	{
		H323Capability	*h323Cap;
		cap_ret_val_t	ret_val;

		if (end_point_exist() == NO) {
			return CAP_EP_ER;
		}

		ret_val = CAP_NSUP_ER;

		/* Create and add the capability */
		h323Cap = h323_capability_create(endPoint, cap, frames);
		if (h323Cap != NULL) {
			endPoint->SetCapability(0, 0, h323Cap);
			//endPoint->SetCapability(P_MAX_INDEX, P_MAX_INDEX, h323Cap);
			WRAPTRACEAPI(2, "Inserted capability " << h323Cap->GetFormatName());
			ret_val = CAP_INSERT_OK;
		} else {
			WRAPTRACEAPI(2, "Failed to insert capability type " << cap);
			ret_val = CAP_NSUP_ER;
		}
		return ret_val;
    }

	/**************************************************************************
	 * Set the mode for sending/receiving user-input (DTMF).
	 */
	cap_ret_val_t
	h323_set_senduimode(uimode_t cap)
	{
		if (end_point_exist() == NO) {
			return CAP_EP_ER;
		}

		/* Set the specified send user-input mode */
		switch (cap) {
			case UIMODE_Q931:
				endPoint->SetSendUserInputMode(H323Connection::SendUserInputAsQ931);
				break;
			case UIMODE_STRING:
				endPoint->SetSendUserInputMode(H323Connection::SendUserInputAsString);
				break;
			case UIMODE_TONE:
				endPoint->SetSendUserInputMode(H323Connection::SendUserInputAsTone);
				break;
			case UIMODE_RFC2833:
				endPoint->SetSendUserInputMode(H323Connection::SendUserInputAsInlineRFC2833);
				break;
			default:
				return CAP_NSUP_ER;
		}

		/* Add all user input capabilities (receiving user-input) */
		endPoint->AddAllUserInputCapabilities(0, P_MAX_INDEX);

		WRAPTRACEAPI(3, "User-input mode set.");
		return CAP_INSERT_OK;
	}

	/**************************************************************************
	 * Start the H.323 listener
	 */
	lis_ret_val_t
	h323_start_listener(lis_type lis, char *listenAddress, int listenPort)
	{
		lis_ret_val_t	ret_val;

		if (end_point_exist() == NO) {
			return LIS_EP_ER;
		}
		ret_val = LIS_NSUP_ER;
		switch (lis) {
			case TCP:
			{
				//PIPSocket::Address interfaceAddress("*");
				PIPSocket::Address interfaceAddress(listenAddress);
				//PIPSocket::Address interfaceAddress(PIPSocket::GetDefaultIpAny());
				H323ListenerTCP *tcpListener = new H323ListenerTCP((H323EndPoint &)*endPoint, interfaceAddress, (WORD)listenPort);
				//H323ListenerTCP *tcpListener = new H323ListenerTCP(*endPoint, interfaceAddress, (WORD)listenPort);
				if (!endPoint->StartListener(tcpListener)) {
					WRAPTRACEAPI(2, "Could not open H.323 TCP listener on " << tcpListener);
					ret_val = LIS_FAILOP_ER;
				} else
					ret_val = LIS_START_OK;
				return ret_val;
			}
			case UDP:
				break;
			default:
				break;
		}
		return ret_val;
	}

	/**************************************************************************
	 * Set the port ranges for the H.323 endpoint
	 */
	int
	h323_set_ports(unsigned tcpBase, unsigned tcpMax, unsigned udpBase, unsigned udpMax,
				unsigned rtpBase, unsigned rtpMax)
	{
		WRAPTRACEAPI(3, "Setting endpoint port ranges.");

		if (end_point_exist() == NO)
			return -1;

		endPoint->SetTCPPorts(tcpBase, tcpMax);
		endPoint->SetUDPPorts(udpBase, udpMax);
		endPoint->SetRtpIpPorts(rtpBase, rtpMax);
		return 0;
	}

	/**************************************************************************
	 * Configure the H.323 endpoint
	 */
	int
	h323_set_options(int noFastStart, int noH245Tunnelling, int noH245inSetup,
				int bwLimit, int jitterMin, int jitterMax,
#ifdef HAS_OH323MODS
				int jitterDec, int jitterInc, 
#endif
				int ipTos)
	{
		WRAPTRACEAPI(3, "Setting endpoint options.");

		if (end_point_exist() == NO)
			return -1;

		if (noFastStart)
			endPoint->DisableFastStart(TRUE);
		else
			endPoint->DisableFastStart(FALSE);
		if (noH245Tunnelling)
			endPoint->DisableH245Tunneling(TRUE);
		else
			endPoint->DisableH245Tunneling(FALSE);
		if (noH245inSetup)
			endPoint->DisableH245inSetup(TRUE);
		else
			endPoint->DisableH245inSetup(FALSE);
		//endPoint->SetInitialBandwidth(bwLimit);
		endPoint->DisableDetectInBandDTMF(TRUE);
		endPoint->SetAudioJitterDelay(jitterMin, jitterMax);
#ifdef HAS_OH323MODS
		endPoint->SetJitterBufferAmount(jitterDec, jitterInc);
#endif
		endPoint->SetRtpIpTypeofService(ipTos);

		return 0;
	}

	/**************************************************************************
     *
     */
	int
	h323_set_gk(char *gkname, char *gkzone, char *gkpass, int gkttl,
				char **alias_tab, int alias_num)
	{
		int i;

		WRAPTRACEAPI(2, "Configuring gatekeeper.");

		if (end_point_exist() == NO)
			return -1;

		/* Set the gatekeeper password */
		if (gkpass != NULL) {
			if (strlen(gkpass)) {
				PString *gkPass = new PString(gkpass);
				endPoint->SetGatekeeperPassword(*gkPass);
			}
		}
		/* Set the gatekeeper TTL */
		endPoint->SetGatekeeperTimeToLive(gkttl);

		/* Set the local user name and our aliases */
		if ((alias_num <= 0)||(alias_tab == NULL)) {
			//PString *pstr = new PString("ASTERISK");
			PString *pstr = new PString("*");
			endPoint->SetLocalUserName(*pstr);
			delete pstr;
		} else {
			PString *pstr;
			pstr = new PString(alias_tab[0]);
			endPoint->SetLocalUserName(*pstr);
			for (i=1; i<alias_num; i++) {
				pstr = new PString(alias_tab[i]);
				endPoint->AddAliasName(*pstr);
				delete pstr;
			}
		}
		return(0);
	}

	int
	h323_reset_gk(char *gkname, char *gkzone)
	{
		if (end_point_exist() == NO)
			return(-1);

#if 0
		GKRegThread gkRegThread(mode, gkname);
		gkRegThread.SetNoAutoDelete();
		gkRegThread.Resume();
		gkRegThread.WaitForTermination();
#endif
		if (gkRegThread == NULL) {
			gkRegThread = new GKRegThread(gkname, gkzone);
		} else {
			gkRegThread->WaitForTermination();
			delete gkRegThread;
			gkRegThread = new GKRegThread(gkname, gkzone);
		}
		gkRegThread->SetNoAutoDelete();
		gkRegThread->Resume();

		return(0);
	}

	/**************************************************************************
	 * Return the Gatekeeper name (ID@hostname) we are registered with.
	 * It is returned in the user supplied buffer. If no gatekeeper is used,
	 * then  is returned. In case of success, 0 is returned and the GK
	 * name is stored in the user supplied buffer. In case of failure
	 * a negative number is returned indicating the type of failure.
	 */
	int
	h323_get_gk(char *gk, int buff_len)
	{
		WRAPTRACEAPI(4, "Checking gatekeeper.");

		if ((end_point_exist() == NO)||(gk == NULL)) {
			return(OH323GK_FAILED);
		}

		H323Gatekeeper *GK = endPoint->GetGatekeeper();
		if (GK != NULL) {
			PString gkname = GK->GetName();
			memset(gk, 0, buff_len);
			strncpy(gk, (const char*)gkname, buff_len - 1);
			if (endPoint->IsRegisteredWithGatekeeper() == FALSE) {
				return(OH323GK_NOTREGISTERED);
			}
		} else {
			return(OH323GK_NOTUSED);
		}
		return(0);
	}

	/**************************************************************************
     *
     */
	int
	h323_get_conn_info(const char *tok, char *buf, int size)
	{
		if (end_point_exist() == NO) {
			return -1;
		}
		PString token = PString(tok);
		endPoint->GetConnectionInfo(token, buf, size);
		return 0;
	}

	/**************************************************************************
     *
     */
	int
	h323_set_hangup_cause(const char *tok, int cause)
	{
		if (end_point_exist() == NO) {
			return -1;
		}
		PString token = PString(tok);
		endPoint->SetClearCallCause(token, cause);
		return 0;
	}

	/**************************************************************************
     *
     */
	unsigned
	h323_check_bandwidth()
	{
		if (end_point_exist() == NO) {
			return 0;
		}
		return endPoint->GetBandwidthAvailable();
	}

	/**************************************************************************
	 * Remove the listener with type name "lis" from the list
	 * with the current listeners. 
	 */
	lis_ret_val_t
	h323_remove_listener(lis_type lis)
	{
		WRAPTRACEAPI(2, "Removing listener.");
		if (end_point_exist() == NO) {
			return LIS_EP_ER;
		}

		/* XXX Remove the actual listener from the endpoint */
		endPoint->RemoveListener(NULL);
		return LIS_REMOVE_OK;
	}

	/**************************************************************************
	 * Remove all listeners from our list.
	 */
	lis_ret_val_t
	h323_removeall_listeners()
	{
		WRAPTRACEAPI(2, "Removing all listeners.");
		if (end_point_exist() == NO) {
			return LIS_EP_ER;
		}
		endPoint->RemoveListener(NULL);
		return LIS_REMOVEALL_OK;
	}

	/**************************************************************************
	 * Remove all capabilities from our list and the endpoint.
	 */
	cap_ret_val_t
	h323_removeall_capabilities()
	{
		WRAPTRACEAPI(2, "Removing all capabilities.");
		if (end_point_exist() == NO) {
			return CAP_EP_ER;
		}
		endPoint->RemoveAllCapabilities();
		return CAP_REMOVEALL_OK;
	}

	/**************************************************************************
	 * Send a DTMF tone over the H323Connection with the
	 * specified token.
	 */
	void
	h323_send_tone(const char *call_token, char tone)
	{
		if (end_point_exist() == NO) {
			return;
		}
		PString token = PString(call_token);
		PString user_input = PString(tone);
		endPoint->SendUserInput(token, user_input);
	}

	/**************************************************************************
	 * Send text message over the H323Connection with the
	 * specified token.
	 */
	void
	h323_send_text(const char *call_token, char *text)
	{
		if (end_point_exist() == NO) {
			return;
		}
		PString token = PString(call_token);
		PString message = "MSG" + PString(text);
		endPoint->SendUserInput(token, message);
	}

	/**************************************************************************
	 * Make a call to the remote end point.
	 */
	call_ret_val_t 
	h323_make_call(char *host, call_details_t *pcd, user_details_t *ud)
	{
		call_ret_val_t	ret_val;
		PString			token;
		unsigned int	call_reference;

		WRAPTRACEAPI(2, "Making call.");

		if (end_point_exist() == NO) {
			return CALL_EP_ER;
		}

		PString dest = PString(host);
		ret_val = endPoint->MakeCall(dest, token, &call_reference, ud);
		memcpy((char *)(pcd->call_token), (const unsigned char *)token, 
				token.GetLength());
		pcd->call_reference = call_reference;

		return ret_val;
	}

	/**************************************************************************
	 * Clear an established call with the remote end point.
	 */
	call_ret_val_t 
	h323_clear_call(const char *call_token)
	{
		call_ret_val_t ret_val = CALL_END_ER;

		WRAPTRACEAPI(2, "Clearing call.");

		if (end_point_exist() == NO) {
			return CALL_EP_ER;
		}

		PString token = PString(call_token);
		if (endPoint->HasConnection(token)) {
			ClearCallThread	*clearCallThread = new ClearCallThread(call_token);
			clearCallThread->Resume();
			ret_val = CALL_END_OK;
		}

		return ret_val;
	}

	/**************************************************************************
	 * Answer a pending call.
	 */
	call_ret_val_t 
	h323_answer_call(const char *call_token)
	{
		call_ret_val_t ret_val;

		WRAPTRACEAPI(2, "Answering call.");

		if (end_point_exist() == NO) {
			return CALL_EP_ER;
		}

		PString token = PString(call_token);
		if (endPoint->AnswerCall(token) == FALSE)
			ret_val = CALL_ANS_ER;
		else
			ret_val = CALL_ANS_OK;

		return ret_val;
	}

	/**************************************************************************
	 * Handle indications on the specified connection.
	 */
	call_ret_val_t 
	h323_indicate_call(const char *call_token, indication_t type)
	{
		call_ret_val_t ret_val;

		WRAPTRACEAPI(2, "Sending indication " << type);

		if (end_point_exist() == NO) {
			return CALL_EP_ER;
		}

		PString token = PString(call_token);
		if (endPoint->IndicateCall(token, type) == FALSE)
			ret_val = CALL_IND_ER;
		else
			ret_val = CALL_IND_OK;

		return ret_val;
	}

	/**************************************************************************
	 * Return the status of a call.
	 */
	int
	h323_is_call_connected(const char *call_token)
	{
		WRAPTRACEAPI(2, "Checking call connection status.");

		if (end_point_exist() == NO) {
			return CALL_EP_ER;
		}

		PString token = PString(call_token);
		return (int)(endPoint->IsConnectionEstablished(token));
	}

	/**************************************************************************
	 * Get the reason of a cleared call.
	 */
	int
	h323_get_reason_code(int reason)
	{
		int i, res;

		res = -1;
		i = 0;
		while (call_end_reason[i].h323_reason != -1) {
			res = call_end_reason[i].wrap_reason;
			if (reason == call_end_reason[i].h323_reason)
				break;
			i++;
		}
		return(res);
	}

	/**************************************************************************
	 * Get the description of the reason of a cleared call.
	 */
	const char *
	h323_get_reason_desc(int reason)
	{
		int i;
		const char *res;

		res = NULL;
		i = 0;
		while (call_end_reason[i].h323_reason != -1) {
			res = call_end_reason[i].desc;
			if (reason == call_end_reason[i].h323_reason)
				break;
			i++;
		}
		return(res);
	}

	/**************************************************************************
	 * Get the description of the Q.931 cause value of a cleared call.
	 */
	const char *
	h323_get_cause_desc(int cause)
	{
		int i;
		const char *res;

		res = NULL;
		i = 0;
		while (call_cause[i].h323_cause != -1) {
			res = call_cause[i].desc;
			if (cause == call_cause[i].h323_cause)
				break;
			i++;
		}
		return(res);
	}

	/**************************************************************************
	 * Change the mode of a running call.
	 */
	call_ret_val_t 
	h323_change_call(const char *call_token, const char *new_mode)
	{
		call_ret_val_t ret_val;

		WRAPTRACEAPI(2, "Changing call.");

		if (end_point_exist() == NO) {
			return CALL_EP_ER;
		}

		PString token = PString(call_token);
		PString mode = PString(new_mode);
		if (endPoint->ChangeMode(token, mode) != TRUE)
			ret_val = CALL_CHG_OK;
		else
			ret_val = CALL_CHG_ER;

		return ret_val;
	}

} /* extern "C" */

// End of file //////////////////////////////////////////////////////////////
